﻿Imports WRDB.DataWeb.HISCentral

''' <summary>
''' Base class of WRDBWebData, used to standardize acquisition of data from web services and web sites
''' </summary>
Public MustInherit Class clsWebProvider

    Public Enum enumProviderType
        Storet
        NwisQW
        NwisDV
        NwisIV
        Ncdc
        Cuahsi
        GaIcis
    End Enum

    ''' <summary>
    ''' This identifies type of datasource, which controls the web service being used to acquire data
    ''' </summary>
    Public ProviderType As enumProviderType

    Public Enum enumNCDCDataType
        Daily
        ISH
        ISD
    End Enum

    ''' <summary>
    ''' This identifies the NCDC dataset to be queried
    ''' </summary>
    Public NCDCDataType As enumNCDCDataType

    ''' <summary>
    ''' List of raw imported data records, prior to mapping of stations and pcodes
    ''' </summary>
    Public lstData As New Generic.List(Of WRDB.Project.clsDataRecord)

    ''' <summary>
    ''' This is the timeout setting in seconds for data acquisition
    ''' </summary>
    ''' <remarks></remarks>
    Public Property Timeout As Integer = 60

#Region "Private classes, dictionaries and lists for organizing support table data..."

    ''' <summary>
    ''' Agency information found in data source referenced by dictAgencies
    ''' </summary>
    Public Class clsAgency
        Public AgencyCode As String
        Public AgencyName As String
        Public Color As String
        Public Letter As String 'letter to use on symbol
        Public Sub New(ByVal _AgencyCode As String, ByVal _AgencyName As String)
            AgencyCode = _AgencyCode
            AgencyName = _AgencyName
        End Sub
    End Class

    ''' <summary>
    ''' PCode information found in data source referenced by dictPCodes
    ''' </summary>
    Public Class clsParameter
        Public Count As Integer
        Public EndDate As Date
        Public ParmID As String
        Public ParmName As String
        Public PCode As String
        Public SiteID As String
        Public StartDate As Date
        Public Units As String
        Public WQX_CharacteristicName As String
        Public WQX_ResultMeasureUnit As String
        Public WQX_ResultSampFraction As String
        Public Sub New(ByVal _SiteID As String, ByVal _ParmID As String, ByVal _PCode As String, ByVal _ParmName As String, ByVal _Units As String, ByVal _Count As Integer,
                       ByVal _StartDate As Date, ByVal _EndDate As Date,
                       Optional ByVal _WQX_CharacteristicName As String = "", Optional ByVal _WQX_ResultSampFraction As String = "", Optional ByVal _WQX_ResultMeasureUnit As String = "")
            SiteID = _SiteID 'only used when getting summary of pcodes by site
            ParmID = _ParmID
            PCode = _PCode
            ParmName = _ParmName
            Units = _Units
            Count = _Count
            StartDate = _StartDate
            EndDate = _EndDate
            WQX_CharacteristicName = _WQX_CharacteristicName
            WQX_ResultSampFraction = _WQX_ResultSampFraction
            WQX_ResultMeasureUnit = _WQX_ResultMeasureUnit
        End Sub
    End Class

    ''' <summary>
    ''' Station information found in data source referenced by dictStations
    ''' </summary>
    Public Class clsStation
        Public AgencyCode As String
        Public AgencyName As String
        Public FIPSCode As String
        Public HUC As String
        Public Latitude As String
        Public Longitude As String
        Public SiteID As String
        Public StationID As String
        Public StationName As String
        Public StationType As String
        Public Elevation As String
        Public Remarks As String
        Sub New(ByVal _SiteID As String, ByVal _StationID As String, ByVal _StationName As String, ByVal _StationType As String,
                ByVal _AgencyCode As String, ByVal _AgencyName As String,
                ByVal _Latitude As String, ByVal _Longitude As String,
                Optional ByVal _FIPSCode As String = "",
                Optional ByVal _HUC As String = "",
                Optional _Elevation As String = "",
                Optional _Remarks As String = "")
            SiteID = _SiteID.Trim
            StationID = _StationID.Trim
            StationName = _StationName.Trim
            StationType = _StationType.Trim
            AgencyCode = _AgencyCode.Trim
            AgencyName = _AgencyName.Trim
            Latitude = _Latitude.Trim
            Longitude = _Longitude.Trim
            FIPSCode = TestNull(_FIPSCode, "").Trim
            HUC = TestNull(_HUC, "").Trim
            Elevation = _Elevation.Trim
            Remarks = _Remarks.Trim
        End Sub
    End Class

    ''' <summary>
    ''' Validation information found in data source referenced by dictValidate
    ''' </summary>
    Public Class clsValidate
        Public FieldName As String
        Public ValidValue As String
        Public Sub New(ByVal _FieldName As String, ByVal _ValidValue As String)
            FieldName = _FieldName
            ValidValue = _ValidValue
        End Sub
    End Class

    'here are dictionaries so can assemble info during initial discovery and export only that associated with selected data
    'each item in station & pcode listviews on this form have tag set to key for dictionary

    ''' <summary>
    ''' Dictionary of Agency Information
    ''' Key: agency code
    ''' Value: agency detailed info
    ''' </summary>
    Public dictAgencies As New Dictionary(Of String, clsAgency)

    ''' <summary>
    ''' Dictionary of Station Information
    ''' Key: siteID
    ''' Value: station detailed info
    ''' </summary>
    Public dictStations As New SortedDictionary(Of String, clsStation)

    ''' <summary>
    ''' Dictionary of Parameter Information
    ''' Key: variable code
    ''' Value: parameter detailed info
    ''' </summary>
    Public dictParameters As New SortedDictionary(Of String, clsParameter)

    ''' <summary>
    ''' Dictionary of Validation Information
    ''' Key: field name ~ valid value
    ''' Value: validation info
    ''' </summary>
    Public dictValidates As New Dictionary(Of String, clsValidate)

    ''' <summary>
    ''' Dictionary of service networks (needed only by CUAHSI)
    ''' Key: network name
    ''' Value: Network information (stored in HISCentral object)
    ''' </summary>
    Public dictNetworks As New Dictionary(Of String, HISCentral.ServiceInfo)

    ''' <summary>
    ''' Dictionary of series in area of interest for selected networks (needed only by CUAHSI)
    ''' Key: variable code
    ''' Value: Series information (stored in HISCentral object)
    ''' </summary>
    Public dictSeries As New SortedDictionary(Of String, HISCentral.SeriesRecord)

    ''' <summary>
    ''' Two-letter country abbreviation (needed only for NCDC service)
    ''' </summary>
    ''' <remarks></remarks>
    Public Property SelectedCountryAbbrev As String

    ''' <summary>
    ''' Two-letter state abbreviation (needed only for NCDC service)
    ''' </summary>
    ''' <remarks></remarks>
    Public Property SelectedStateAbbrev As String

#End Region

#Region "Inheritable Methods"

    Public MustOverride ReadOnly Property Description As String

    Public MustOverride ReadOnly Property URL As String

    Public Overridable Property AreaOfInterest As Drawing.RectangleF

    Public Sub SetAreaOfInterest(AOI As Drawing.RectangleF)
        LastException = Nothing
        Cancelled = False
        AreaOfInterest = AOI
    End Sub

    ''' <summary>
    ''' Initialization required before retrieving data count or data
    ''' </summary>
    ''' <param name="_StartDate"></param>
    ''' <param name="_EndDate"></param>
    ''' <param name="_SelectedCountry"></param>
    ''' <param name="_SelectedState"></param>
    ''' <param name="_SelectedNetworkIDs"></param>
    ''' <param name="_SelectedStations"></param>
    ''' <param name="_SelectedParameters"></param>
    ''' <param name="_SelectedSeries"></param>
    ''' <remarks></remarks>
    Public Sub Initialize(_StartDate As Date,
                          _EndDate As Date,
                          _NCDCDataType As enumNCDCDataType,
                          _SelectedCountry As String,
                          _SelectedState As String,
                          _SelectedNetworkIDs As Generic.List(Of Integer),
                          _SelectedStations As Generic.List(Of String),
                          _SelectedParameters As Generic.List(Of String),
                          _SelectedSeries As Generic.List(Of SeriesRecord))

        StartDate = _StartDate
        EndDate = _EndDate
        NCDCDataType = _NCDCDataType
        If ProviderType = enumProviderType.Ncdc AndAlso _SelectedCountry <> "" And _SelectedState <> "" Then
            SelectedCountryAbbrev = GetCountryCodes(_SelectedCountry.ToUpper)
            SelectedStateAbbrev = GetStateCodes(_SelectedState)
        End If

        SelectedNetworkIDs = _SelectedNetworkIDs
        SelectedStations = _SelectedStations
        SelectedParameters = _SelectedParameters
        SelectedSeries = _SelectedSeries

        LastException = Nothing
        Cancelled = False
    End Sub

    Public Property StartDate As Date

    Public Property EndDate As Date

    Public Property SelectedStations As New Generic.List(Of String)

    Public Property SelectedParameters As New Generic.List(Of String)

    Public Property SelectedSeries As New Generic.List(Of HISCentral.SeriesRecord)

    ''' <summary>
    ''' List of selected network IDs (integers); must be set before calling GetStations (only for Cuahsi)
    ''' </summary>
    Public Property SelectedNetworkIDs As New Generic.List(Of Integer)

    Public Property DataCount As Integer

    Public Property LastException As Exception

    Public MustOverride Function GetStations(Optional NameFilter As String = "") As Boolean

    Public MustOverride Function GetParameters(Optional NameFilter As String = "") As Boolean

    Public MustOverride Function GetResults() As Boolean

    'Public Function GetParametersAsync(Optional NameFilter As String = "") As Boolean

    'End Function

    'Public Function GetResultsAsync() As Boolean

    'End Function

    Public Overridable Function GetResultCount(QuickCount As Boolean) As Boolean
        DataCount = -1
        Return True
    End Function

#End Region

    ''' <summary>
    ''' Dictionary containing lookup of FIPS code
    ''' Key: county ~ state
    ''' Value: FIPS code
    ''' </summary>
    Public Property dictFIPS As Generic.Dictionary(Of String, String)

    Private ProgressPct As Integer, ProgressText As String, ProgressUpdated As Boolean = False

    Protected Function SetProgress(Count As Integer, MaxCount As Integer, Text As String) As Boolean
        ProgressUpdated = True
        ProgressPct = CInt(Count * 100.0 / IIf(MaxCount = 0, 1, MaxCount))
        ProgressText = Text
        Return Not Cancelled
    End Function

    Protected Function SetProgress(Text As String) As Boolean
        Return SetProgress(0, 1, Text)
    End Function

    ''' <summary>
    ''' Thread-safe method to get progress information for async operations
    ''' </summary>
    ''' <param name="Pct">Percent complete</param>
    ''' <param name="Text">Text to display</param>
    ''' <returns>True if progress has changed and needs to be updated</returns>
    Public Function GetProgress(ByRef Pct As Integer, ByRef Text As String) As Boolean
        If ProgressUpdated Then
            Pct = ProgressPct
            Text = ProgressText
            ProgressUpdated = False
            Return True
        Else
            Return False
        End If
    End Function

    Public Property Cancelled As Boolean = False

    Protected Function ColumnExists(ByRef Dataset As DataSet, ByVal TableName As String, ByVal ColumnName As String) As Boolean
        Return Dataset IsNot Nothing AndAlso
               Dataset.Tables.Contains(TableName) AndAlso
               Dataset.Tables(TableName).Columns.Contains(ColumnName)
    End Function

    Protected Function GetValue(dr As DataRow, ColumnName As String, Optional DefaultValue As Object = "") As Object
        If dr.Table.Columns.Contains(ColumnName) Then
            Return TestNull(dr(ColumnName), DefaultValue)
        Else
            Return DefaultValue
        End If
    End Function

    ''' <summary>
    ''' Test object; if dbnull, nothing or empty string, return default value
    ''' </summary>
    Shared Function TestNull(ByVal Value As Object, ByVal DefaultValue As Object) As Object
        If IsDBNull(Value) OrElse IsNothing(Value) OrElse (TypeOf Value Is String AndAlso Value = "") OrElse (IsNumeric(Value) AndAlso Value = Single.MinValue) Then Return DefaultValue Else Return Value
    End Function

    Protected Sub AddValidate(ByVal FieldName As String, ByVal ValidValue As String)
        Dim key As String = FieldName & " ~ " & ValidValue
        If ValidValue <> "" AndAlso Not dictValidates.ContainsKey(key) Then dictValidates.Add(key, New clsValidate(FieldName, ValidValue))
    End Sub

    ''' <summary>
    ''' Retrieve NWIS query results using REST
    ''' </summary>
    ''' <param name="URL">Full URL to REST service including all query parameters</param>
    ''' <returns>Dataset with results</returns>
    '<DebuggerStepThrough()> _
    Protected Function DownloadNwisData(URL As String, ByRef ds As DataSet) As Boolean
        Dim tempfile As String = My.Computer.FileSystem.GetTempFileName
        Try
            ds = New DataSet
            If Not DownloadWebPage(URL, tempfile) Then Return False
            If IO.File.ReadAllText(tempfile).StartsWith("Error 400") Then Return True 'no data that satisfy request
            ds.ReadXml(tempfile)
            Return True
        Catch ex As Xml.XmlException
            ds = Nothing
            Return True 'occurs if tempfile is empty (no data)
        Catch ex As Exception
            If ex.Message.Contains("(404)") Then
                Return True
            Else
                Return False
                Throw
            End If
        Finally
            My.Computer.FileSystem.DeleteFile(tempfile)
        End Try
    End Function

    ''' <summary>
    ''' Retrieve NWIS query results using REST
    ''' </summary>
    ''' <param name="URL">Full URL to REST service including all query parameters</param>
    ''' <returns>Dataset with results</returns>
    Protected Function DownloadNwisData(URL As String, ByRef FileContents As String) As Boolean
        Try
            Dim tempfile As String = My.Computer.FileSystem.GetTempFileName
            If Not DownloadWebPage(URL, tempfile) Then Return False
            FileContents = My.Computer.FileSystem.ReadAllText(tempfile)
            My.Computer.FileSystem.DeleteFile(tempfile)
            Return True
        Catch ex As Exception
            FileContents = ""
            Return False
        End Try
    End Function

    ''' <summary>
    ''' This routine is used to download list of characteristic names for NWISWQ
    ''' </summary>
    ''' <param name="URL">URL where list is found</param>
    ''' <param name="FileName">Name of file to save XML document to</param>
    Protected Function DownloadWebPage(ByVal URL As String, ByVal FileName As String) As Boolean
        If Not UCase(URL) Like "HTTP://*" Then URL = "http://" & URL

        Try
            Dim objReq As Net.HttpWebRequest = Net.WebRequest.Create(New Uri(URL))
            With objReq
                .Timeout = Timeout * 1000 'timeout property is in milliseconds

                'use following for proxy server...

                'If ProxyServer <> "" Then
                '    .Proxy = New WebProxy(ProxyServer, True)
                '    .Proxy.Credentials = Net.CredentialCache.DefaultCredentials
                '    .UserAgent = "DHMS"
                '    .Headers.Add("Pragma:No-Cache")
                'End If

                Dim resp As Net.WebResponse = .GetResponse

                Dim fs As New IO.FileStream(FileName, IO.FileMode.Create)
                Dim rs As IO.Stream = resp.GetResponseStream()

                ' Create a local buffer for a temporary storage of the read data.
                Dim readBuffer(1023) As Byte

                ' Read the first 1024 bytes.
                Dim count As Integer = rs.Read(readBuffer, 0, readBuffer.Length)
                'Loop to read the remaining data in blocks of 1024 bytes
                While count > 0
                    fs.Write(readBuffer, 0, count)
                    count = rs.Read(readBuffer, 0, readBuffer.Length)
                End While
                fs.Close()
                rs.Close()
            End With
            Return True
        Catch ex As Net.WebException
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Retrieve a dictionary of country names and codes used to narrow the selection of stations by NCDC
    ''' </summary>
    Public Shared Function GetCountryCodes() As Generic.Dictionary(Of String, String)
        Dim dict As New Generic.Dictionary(Of String, String)
        For Each line As String In My.Resources.CountryCodes.Replace(vbLf, "").Split(vbCr)
            Dim ar() As String = line.Split(";")
            If ar.Length = 2 Then dict.Add(ar(0), ar(1))
        Next
        Return dict
    End Function

    ''' <summary>
    ''' Retrieve a dictionary of state names and codes used to narrow the selection of stations by NCDC
    ''' </summary>
    Public Shared Function GetStateCodes() As Generic.Dictionary(Of String, String)
        Dim dict As New Generic.Dictionary(Of String, String)
        For Each line As String In My.Resources.StateCodes.Replace(vbLf, "").Split(vbCr)
            Dim ar() As String = line.Split(";")
            If ar.Length = 2 Then dict.Add(ar(0), ar(1))
        Next
        Return dict
    End Function

    ''' <summary>
    ''' Instantiate a new web provider of the specified type; note that you must set a number of properties before making calls
    ''' </summary>
    ''' <param name="ProviderType">Type of web data source</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetWebProvider(ProviderType As enumProviderType) As clsWebProvider
        Dim WB As clsWebProvider = Nothing
        Select Case ProviderType
            Case enumProviderType.Cuahsi : WB = New clsCuahsi
            Case enumProviderType.GaIcis : WB = New clsIcis
            Case enumProviderType.NwisDV : WB = New clsNwisDv
            Case enumProviderType.NwisIV : WB = New clsNwisIV
            Case enumProviderType.NwisQW : WB = New clsNwisQw
            Case enumProviderType.Ncdc : WB = New clsNcdc
            Case enumProviderType.Storet : WB = New clsStoret
        End Select
        WB.ProviderType = ProviderType
        Return WB
    End Function

    Public Sub Cancel()
        Cancelled = True
    End Sub
End Class
